﻿using System;
using System.Diagnostics;
using System.Threading;
using NLog;

namespace ChangePlaybackDevice.BC
{
	/// <summary>
	/// 再生デバイスの変更クラス.
	/// </summary>
	public class PlaybackDeviceService
	{
		/// <summary>
		/// Logger.
		/// </summary>
		private static Logger log = LogManager.GetCurrentClassLogger();

		// ----------------------------------------------------------------
		// NOTE:サウンドコントロールパネルの画面構成
		// ----------------------------------------------------------------
		// サウンド画面
		// サウンド画面.再生タブ
		// サウンド画面.再生タブ.デバイスリスト     ←(1)クリック
		// サウンド画面.再生タブ.既定値に設定ボタン ←(2)クリック
		// サウンド画面.OKボタン                    ←(3)クリック
		// ----------------------------------------------------------------

		/// <summary>
		/// 再生デバイスを変更する.
		/// </summary>
		/// <param name="deviceIndex">選択するデバイス番号（上から1..5）</param>
		public void Change(int deviceIndex)
		{
			log.Debug("Method start.");

			// 閾値チェック
			log.Debug("Device index = {0}", deviceIndex);
			if (deviceIndex < 1 || deviceIndex > 5)
			{
				log.Error("Device index error.");
				throw new ArgumentOutOfRangeException("Invalid deviceIndex. (1..5)");
			}

			// サウンドコントロールパネルを開く
			log.Debug("Open the Playback device settings.");
			Process.Start("mmsys.cpl", ",,playback");

			// サウンド画面（30秒の猶予）
			IntPtr hwndSound = this.findWindow(IntPtr.Zero, null, "サウンド", 30);
			// サウンド> 再生タブ
			IntPtr hwndPlayTab = this.findWindow(hwndSound, null, "再生");
			// サウンド> 再生タブ> デバイスリスト項目
			IntPtr hwndDeviceList = this.findWindow(hwndPlayTab, "SysListView32", null);
			// サウンド> 再生タブ> 既定値に設定ボタン
			IntPtr hwndBtnDefault = this.findWindow(hwndPlayTab, "Button", "既定値に設定(&S)");
			// サウンド> OKボタン
			IntPtr hWndBtnOk = this.findWindow(hwndSound, "Button", "OK");


			// デバイスに応じてクリック対象のポイントを設定しクリック
			uint deviceAxisY = (uint)(57 * (deviceIndex - 1) + 4 + (57 / 2)); // 各リストのHeight=57, Margin=4
			this.mouseClick(hwndDeviceList, 64, deviceAxisY);
			// 既定値に設定ボタンをクリック
			this.mouseClick(hwndBtnDefault, 8, 8);
			// OKボタンをクリック
			this.mouseClick(hWndBtnOk, 8, 8);

			log.Debug("Method finished.");
		}

		/// <summary>
		/// 指定hWndに対してマウスクリックイベントをPostする。（x,y座標形式で指定）
		/// </summary>
		/// <param name="hWnd"></param>
		/// <param name="x"></param>
		/// <param name="y"></param>
		private void mouseClick(IntPtr hWnd, uint x, uint y)
		{
			uint point = ((0x0000FFFF & y) << 16) | (0x0000FFFF & x);
			this.mouseClick(hWnd, (int)point);
		}

		/// <summary>
		/// 指定hWndに対してマウスクリックイベントをPostする.
		/// </summary>
		/// <param name="hWnd">Window handle.</param>
		/// <param name="point">lParam (y,x axis)</param>
		private void mouseClick(IntPtr hWnd, int point)
		{
			log.Debug("Post mouse click message: lParam=0x{0:x8}", point);
			Win32Wrapper.PostMessage(hWnd, Win32Wrapper.WM_LBUTTONDOWN, 0x00000001, point);
			Win32Wrapper.PostMessage(hWnd, Win32Wrapper.WM_LBUTTONUP, 0x00000000, point);
		}

		/// <summary>
		/// 指定ウィンドウの検索を行う.
		/// タイムアウト時間までに見つからない場合はWindowNotFoundExceptionをthrowする.
		/// </summary>
		/// <param name="hwndParent"></param>
		/// <param name="className"></param>
		/// <param name="windowTitle"></param>
		/// <param name="timeoutSeconds">タイムアウト秒数（デフォルト=5秒）</param>
		/// <returns></returns>
		private IntPtr findWindow(IntPtr hwndParent, string className, string windowTitle, double timeoutSeconds = 5)
		{
			// タイムアウト時間の設定
			DateTime timeout = DateTime.Now.AddSeconds(timeoutSeconds);

			// ウィンドウの検索ループ
			log.Debug("Call the FindWindowEx: Parent=0x{0:x8}, Class=[{1}], Window=[{2}]", hwndParent, className, windowTitle);
			while (true)
			{
				// ウィンドウの取得
				IntPtr hwnd = Win32Wrapper.FindWindowEx(hwndParent, IntPtr.Zero, className, windowTitle);

				// ウィンドウが見つかった場合
				if (hwnd != IntPtr.Zero)
				{
					log.Debug("         Return value: hWnd  =0x{0:x8}", hwnd);
					return hwnd;
				}

				// タイムアウト確認
				if (timeout.CompareTo(DateTime.Now) < 0)
				{
					log.Error("Window not found.");
					throw new WindowNotFoundException();
				}

				// ウェイト
				Thread.Sleep(100);
			}
		}

	}
}
